package org.xteam.sled.semantic;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.xteam.parser.runtime.Span;
import org.xteam.sled.model.AbsoluteProtoPattern;
import org.xteam.sled.model.BasePattern;
import org.xteam.sled.semantic.exp.Exp;
import org.xteam.sled.semantic.exp.ExpCondition;
import org.xteam.sled.semantic.exp.IExpRewriter;


public class Pattern implements IDenotable {
	
	private String name;
	private List<Disjunct> disjuncts = new ArrayList<Disjunct>();
	
	public Pattern() {
	}
	
	public Pattern(String name) {
		this.name = name;
	}
	
	public Pattern(Disjunct disjunct) {
		this(null, disjunct);
	}

	public Pattern(String name, Disjunct dis) {
		this.name = name;
		this.disjuncts.add(dis);
	}
	
	public Pattern(List<Disjunct> disjuncts) {
		this(null, disjuncts);
	}
	
	public Pattern(String name, List<Disjunct> disjuncts) {
		this.name = name;
		this.disjuncts = new ArrayList<Disjunct>(disjuncts	);
	}

	public void add(Disjunct disjunct) {
		disjuncts.add(disjunct);
	}
	
	public List<Disjunct> getDisjunctions() {
		return disjuncts;
	}
	
	public String name() {
		return name;
	}
	
	public Pattern or(Pattern other) {
		Pattern p = new Pattern();
		p.disjuncts.addAll(disjuncts);
		p.disjuncts.addAll(other.disjuncts);
		return p;
	}
	
	public Pattern and(Pattern other) {
		Pattern p = distribute(new IBinaryFunc<Disjunct>() {
			public Disjunct exec(Disjunct h, Disjunct d) {
				return h.and(d);
			}
		}, other);
		p.dropContradictoryDisjuncts();
		String keepname = isFieldBinding() ? name : other.isFieldBinding() ? other.name : null;
		if (keepname != null && p.name == null)
			p.name = keepname;
		return p;
	}
	
	public Pattern seq(Pattern other) {
		return distribute(new IBinaryFunc<Disjunct>() {
			public Disjunct exec(Disjunct h, Disjunct d) {
				return h.seq(d);
			}
		}, other);
	}
	
	public Pattern preDots() {
		Pattern p = new Pattern(name);
		for (Disjunct d : disjuncts) {
			p.add(d.preDots());
		}
		return p;
	}

	public Pattern postDots() {
		Pattern p = new Pattern(name);
		for (Disjunct d : disjuncts) {
			p.add(d.postDots());
		}
		return p;
	}
	
	private void dropContradictoryDisjuncts() {
		Iterator<Disjunct> i = disjuncts.iterator();
		while (i.hasNext()) {
			if (i.next().getSequence().isContradictory())
				i.remove();
		}
	}

	private Pattern distribute(IBinaryFunc<Disjunct> opdd, Pattern other) {
		Pattern result = new Pattern();
		for (Disjunct h : disjuncts) {
			for (Disjunct d : other.disjuncts) {
				result.add(opdd.exec(h, d));
			}
		}
		return result;
	}
	
	public boolean isFieldBinding () {
		if (disjuncts.size() == 1 && ! disjuncts.get(0).hasConditions()) {
			Sequence seq = disjuncts.get(0).getSequence();
			if (! seq.hasPrefix() && ! seq.hasSuffix() && seq.getElements().size() == 1) {
				SequenceElement cnst = seq.getElements().get(0);
				return cnst.getConstraints().size() == 1;
			}
		}
		return false;
	}

	
	public void setName(String name) {
		if (disjuncts.size() == 1 && disjuncts.get(0).name() != null) {
			disjuncts.get(0).setName(name);
			this.name = name;
		} else {
			for (Disjunct dis : disjuncts) {
				if (dis.name() == null) {
					dis.setName(name);
				}
			}
			if (this.name == null)
				this.name = name;
		}
	}
	
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("[");
		if (name != null) {
			buffer.append(name);
			buffer.append(":");
		}
		boolean isFirst = true;
		for (Disjunct dis : disjuncts) {
			if (isFirst)
				isFirst = false;
			else
				buffer.append("|");
			buffer.append(dis.toString());
		}
		buffer.append("]");
		return buffer.toString();
	}

	@Override
	public Exp projectInt(IEvaluationContext context, Span span) {
		context.reportError(span, "not an integer expression");
		return null;
	}
	
	@Override
	public BasePattern projectPattern(IEvaluationContext context, Span span) {
		throw new RuntimeException("burp"); // P.injectPure
	}

	public void addConditions(List<ExpCondition> constraints) {
		for (Disjunct dis : disjuncts) {
			dis.addConditions(constraints);
		}
	}

	public void makeLabelsPCRelative() {
		for (Disjunct dis : disjuncts) {
			dis.makeLabelsPCRelative();
		}
	}

	public Pattern injectLatent() {
		Pattern n = new Pattern(name);
		for (Disjunct d : disjuncts) {
			Disjunct dis = d.injectLatent();
			n.add(dis);
		}
		return n;
	}
	
	// apply on Labelled
	public Pattern actualizeLatent(boolean actualsOk) {
		Pattern n = new Pattern(name);
		for (Disjunct d : disjuncts) {
			Disjunct dis = d.actualizeLatent(actualsOk);
			n.add(dis);
		}
		return n;
	}
	
	public Pattern subst(IExpRewriter subs) {
		Pattern n = new Pattern(name);
		for (Disjunct d : disjuncts) {
			Disjunct dis = d.subst(subs);
			boolean hasFalse = false;
			for (Exp e : dis.getConditions()) {
				// Exp.compare(e, Exp.false') = EQUAL
				throw new RuntimeException();
			}
			if (! hasFalse)
				n.add(dis);
		}
		return n;
	}

	public Pattern nameUnnamed(String newName) {
		Pattern n = new Pattern(name == null ? newName : name);
		for (Disjunct d : disjuncts) {
			n.add(d.nameUnnamed(newName));
		}
		return n;
	}

	public AbsoluteProtoPattern anf() {
		throw new RuntimeException("must convert to new model");
	}

}
